﻿/*
 * @author: wizardc
 */

using System;
using DG.Tweening;
using UnityEngine;

namespace Dou.Controller
{
    /// <summary>
    /// 固定机位的限制类型
    /// </summary>
    internal enum FixedCameraLimitType
    {
        /// <summary>
        /// 矩形
        /// </summary>
        Rect,
        /// <summary>
        /// 梯形
        /// </summary>
        Trapezoid
    }

    /// <summary>
    /// 高度设定
    /// </summary>
    public struct FixedCameraHeightSetting
    {
        /// <summary>
        /// 高度
        /// </summary>
        public float height;
        /// <summary>
        /// x 轴角度
        /// </summary>
        public float degree;
        /// <summary>
        /// 高度拉伸速度（相机越高缩放时需要增加/减少的高度越多）
        /// </summary>
        public float speed;
    }
    
    /// <summary>
    /// 固定机位的摄像机控制器，多用于 SLG 的大地图控制
    /// 
    /// - 摄像机类型必须为透视相机；
    /// - 限定区域为摄像机的可视区域而非摄像机位置；
    /// - 支持各种事件来得到摄像机的当前状态、支持各种接口来获取可视区域的信息；
    /// - 可使用限制：摄像机 y 轴必须高于地面且 x 轴旋转为 30 到 90 度之间且 yz 轴旋转必须为 0；
    /// 
    /// 待完善：
    /// 1. 大地图缩放时不跟手，只能调节参数尽可能使其跟手，需要找到跟手的算法
    /// 2. 不同的设备屏幕分辨率是不同的，所以要统一下类似移动速度等参数
    /// </summary>
    [RequireComponent(typeof(Camera))]
    public class FixedCameraController : MonoBehaviour
    {
        /// <summary>
        /// 相机位置或高度变动时
        /// </summary>
        public event Action<FixedCameraController> onPositionChanged;
        
        /// <summary>
        /// 开始拖动时
        /// </summary>
        public event Action<FixedCameraController> onMoveBegin;
        
        /// <summary>
        /// 拖动中
        /// </summary>
        public event Action<FixedCameraController> onMove;
        
        /// <summary>
        /// 结束拖动时
        /// </summary>
        public event Action<FixedCameraController> onMoveEnd;
        
        /// <summary>
        /// 开始缩放时
        /// </summary>
        public event Action<FixedCameraController> onScaleBegin;
        
        /// <summary>
        /// 缩放中
        /// </summary>
        public event Action<FixedCameraController> onScale;
        
        /// <summary>
        /// 结束缩放时
        /// </summary>
        public event Action<FixedCameraController> onScaleEnd;
        
        private Camera _camera;
        
        // 视口相关坐标信息
        private Vector3[] _viewportPos;
        // 限制区域相关坐标信息
        private Vector3[] _limitPos;

        private FixedCameraLimitType _limitType;

        private FixedCameraHeightSetting[] _heightSettings;
        private float _minHeight;
        private float _maxHeight;

        private Ease _inertiaEase;
        private float _inertiaEffectiveTime;
        private Vector2 _inertia;
        private Vector2 _inertiaSpeed;
        private float _inertiaTime;

        private float _height;

        private bool _moving;
        private Vector3 _lastCameraPos;
        private Vector3 _lastFloorPos;

        private Tweener _moveTweener;
        private float _tweenLastTime;
        private Vector3 _tweenVector;

        private Vector3 _lastPinchPos;
        private Tweener _pinchTweener;
        
        public FixedCameraController()
        {
            _viewportPos = new []
            {
                Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero,
                Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero
            };
            _limitPos = new []
            {
                Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero
            };
        }

        /// <summary>
        /// 关联的摄像机对象
        /// </summary>
        public Camera camera => _camera;
        
        /// <summary>
        /// 视口相关坐标信息
        /// [0]矩形/梯形左上角，[1]矩形/梯形右上角，[2]梯形左下角，[3]梯形右下角，[4]矩形左下角，[5]矩形右下角，[6]屏幕中心点，[7]空间中心点
        /// </summary>
        public Vector3[] viewportPos => _viewportPos;

        /// <summary>
        /// 限制区域相关坐标信息
        /// [0]左上，[1]右上，[2]左下，[3]右下
        /// </summary>
        public Vector3[] limitPos => _limitPos;
        
        void Awake()
        {
            _camera = GetComponent<Camera>();
        }

        /// <summary>
        /// 初始化矩形限制框
        /// </summary>
        public void InitRectLimit(Vector3 min, Vector3 max)
        {
            _limitType = FixedCameraLimitType.Rect;
            _limitPos[0] = new Vector3(min.x, 0, max.z);
            _limitPos[1] = max;
            _limitPos[2] = min;
            _limitPos[3] = new Vector3(max.x, 0, min.z);
        }

        /// <summary>
        /// 初始化梯形限制框
        /// </summary>
        public void InitTrapezoidLimit(Vector3 topLeft, Vector3 topRight, Vector3 bottomLeft, Vector3 bottomRight)
        {
            _limitType = FixedCameraLimitType.Trapezoid;
            _limitPos[0] = topLeft;
            _limitPos[1] = topRight;
            _limitPos[2] = bottomLeft;
            _limitPos[3] = bottomRight;
        }

        /// <summary>
        /// 初始化高度设定，按高度值从小到大排序传入
        /// </summary>
        public void InitHeightSetting(FixedCameraHeightSetting[] settings)
        {
            _heightSettings = settings;
            _minHeight = _heightSettings[0].height;
            _maxHeight = _heightSettings[^1].height;
            // 将摄像机初始化为最小高度的状态
            var setting = _heightSettings[0];
            transform.position = new Vector3(0, setting.height, 0);
            transform.rotation = Quaternion.Euler(setting.degree, 0, 0);
        }
        
        /// <summary>
        /// 初始化移动缓动效果设定
        /// </summary>
        /// <param name="effectiveTime">生效时间，即调用 BeginMove 后在该时间内调用 EndMove 才会有惯性滑动效果</param>
        public void InitMoveTween(Ease ease, float effectiveTime, Vector2 inertia, Vector2 inertiaSpeed, float inertiaTime)
        {
            _inertiaEase = ease;
            _inertiaEffectiveTime = effectiveTime;
            _inertia = inertia;
            _inertiaSpeed = inertiaSpeed;
            _inertiaTime = inertiaTime;
        }
        
        /// <summary>
        /// 立即移动到指定位置，该位置是世界坐标
        /// </summary>
        public void MoveTo(float x, float z)
        {
            StopMoveTween();
            StopScaleTween();
            
            var pos = FixedCameraUtil.GetGroundToCameraPosition(_camera, x, z, transform.position.y);
            transform.position = pos;
            FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
            CheckLimit();
        }

        private bool CheckLimit()
        {
            var pos = transform.position;
            if (_limitType == FixedCameraLimitType.Rect)
            {
                var screenCenterPos = FixedCameraUtil.RectLimit(_viewportPos, _limitPos[2], _limitPos[1]);
                if (Vector3.Distance(_viewportPos[6], screenCenterPos) >= 0.01f)
                {
                    pos = FixedCameraUtil.GetGroundToCameraPosition(_camera, screenCenterPos.x, screenCenterPos.z, transform.position.y);
                    transform.position = pos;
                    _lastCameraPos = transform.position;
                    FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
                    return true;
                }
            }
            else
            {
                var screenCenterPos = FixedCameraUtil.TrapezoidLimit(_viewportPos, _limitPos[0], _limitPos[1], _limitPos[2], _limitPos[3]);
                if (Vector3.Distance(_viewportPos[6], screenCenterPos) >= 0.01f)
                {
                    pos = FixedCameraUtil.GetGroundToCameraPosition(_camera, screenCenterPos.x, screenCenterPos.z, transform.position.y);
                    transform.position = pos;
                    _lastCameraPos = transform.position;
                    FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
                    return true;
                }
            }
            return false;
        }
        
        /// <summary>
        /// 持续移动，该位置为屏幕坐标
        /// </summary>
        public void Move(float x, float y)
        {
            var pos = FixedCameraUtil.ScreenPointToGroundPoint(_camera, x, y);
            if (_moving)
            {
                var move = _lastFloorPos - pos;
                transform.position = _lastCameraPos + move;
                _lastCameraPos = transform.position;
                _lastFloorPos = FixedCameraUtil.ScreenPointToGroundPoint(_camera, x, y);
                FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
                
                _tweenLastTime = Time.realtimeSinceStartup;
                _tweenVector = move;
                
                CheckLimit();
                
                onMove?.Invoke(this);
                onPositionChanged?.Invoke(this);
            }
            else
            {
                StopMoveTween();
                StopScaleTween();
                
                _moving = true;
                _lastCameraPos = transform.position;
                
                _tweenLastTime = Time.realtimeSinceStartup;
                _lastFloorPos = pos;
                
                onMoveBegin?.Invoke(this);
            }
        }

        /// <summary>
        /// 结束持续移动
        /// 调用后会触发后续的缓动效果，需要先调用 BeginMove 方法
        /// </summary>
        public void EndMove()
        {
            if (_moving)
            {
                _moving = false;
                onMoveEnd?.Invoke(this);
                
                StartMoveTween();
            }
        }

        private void StartMoveTween()
        {
            if (_moveTweener != null)
            {
                return;
            }

            if (Time.realtimeSinceStartup - _tweenLastTime > _inertiaEffectiveTime)
            {
                return;
            }
            
            var ratio = (transform.position.y - _minHeight) / (_maxHeight - _minHeight);
            var inertia = Mathf.Lerp(_inertia.x, _inertia.y, ratio);
            var inertiaSpeed = Mathf.Lerp(_inertiaSpeed.x, _inertiaSpeed.y, ratio);
            _tweenVector.y = 0;
            _tweenVector *= inertia;
            var distance = _tweenVector.magnitude;
            var time = distance / inertiaSpeed;
            var dist = transform.position + _tweenVector;
            JumpToPos(dist.x, dist.z, time > _inertiaTime ? _inertiaTime : time, _inertiaEase);
        }
        
        /// <summary>
        /// 立即设定相机高度值
        /// </summary>
        public void ScaleTo(float height)
        {
            StopMoveTween();
            StopScaleTween();

            SetHeight(height);
        }

        private void SetHeight(float height)
        {
            var setting = GetCurrentSetting(height);
            var pos = transform.position;
            pos.y = setting.height;
            transform.position = pos;
            transform.rotation = Quaternion.Euler(setting.degree, 0, 0);

            FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
            CheckLimit();
        }

        private FixedCameraHeightSetting GetCurrentSetting(float height)
        {
            height = Mathf.Clamp(height, _minHeight, _maxHeight);

            var nowLevel = 0;
            for (var i = _heightSettings.Length - 2; i >= 0; i--)
            {
                if (height >= _heightSettings[i].height)
                {
                    nowLevel = i;
                    break;
                }
            }

            var ratio = (height - _heightSettings[nowLevel].height) / (_heightSettings[nowLevel + 1].height - _heightSettings[nowLevel].height);
            var setting = new FixedCameraHeightSetting
            {
                height = height,
                degree = Mathf.Lerp(_heightSettings[nowLevel].degree, _heightSettings[nowLevel + 1].degree, ratio),
                speed = Mathf.Lerp(_heightSettings[nowLevel].speed, _heightSettings[nowLevel + 1].speed, ratio)
            };
            
            return setting;
        }

        /// <summary>
        /// 持续缩放
        /// </summary>
        public void Scale(float x, float y, float deltaPinch)
        {
            StopMoveTween();
            StopScaleTween();

            if (_lastPinchPos == default)
            {
                _lastPinchPos = FixedCameraUtil.ScreenPointToGroundPoint(_camera, x, y);
                onScaleBegin?.Invoke(this);
            }
            
            var setting = GetCurrentSetting(transform.position.y);
            float height = setting.height + -deltaPinch * setting.speed;
            height = Mathf.Clamp(height, _minHeight, _maxHeight);
            if ((setting.height == _maxHeight && height > setting.height) || (setting.height == _minHeight && height < setting.height))
            {
                return;
            }
            
            transform.rotation = Quaternion.Euler(setting.degree, 0, 0);
            
            var pos = FixedCameraUtil.GetGroundToCameraPosition(_camera, _lastPinchPos.x, _lastPinchPos.z, height);
            transform.position = pos;
            var groundPos = FixedCameraUtil.ScreenPointToGroundPoint(_camera, x, y);
            pos -= groundPos - _lastPinchPos;
            transform.position = pos;
            
            FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
            CheckLimit();
            
            onScale?.Invoke(this);
            onPositionChanged?.Invoke(this);
        }

        /// <summary>
        /// 结束持续缩放
        /// </summary>
        public void EndScale()
        {
            _lastPinchPos = default;
            onScaleEnd?.Invoke(this);
        }
        
        /// <summary>
        /// 跳转到指定位置
        /// </summary>
        public void JumpToPos(float x, float z, float time, Ease ease, Action onComplete = null)
        {
            StopMoveTween();
            
            _moveTweener = transform.DOMove(new Vector3(x, transform.position.y, z), time);
            _moveTweener.SetEase(ease);
            _moveTweener.OnUpdate(() =>
            {
                FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
                if (CheckLimit())
                {
                    // 允许继续缓动
                    // StopMoveTween();
                    // onComplete?.Invoke();
                }
                onPositionChanged?.Invoke(this);
            });
            _moveTweener.OnComplete(() =>
            {
                FixedCameraUtil.CalculateViewport(_camera, _viewportPos);
                CheckLimit();
                StopMoveTween();
                onPositionChanged?.Invoke(this);
                onComplete?.Invoke();
            });
            _moveTweener.Play();
        }

        private void StopMoveTween()
        {
            if (_moveTweener != null)
            {
                _moveTweener.Kill();
                _moveTweener = null;
            }
        }

        /// <summary>
        /// 跳转到指定高度
        /// </summary>
        public void JumpToHeight(float height, float time, Ease ease, Action onComplete = null)
        {
            height = Mathf.Clamp(height, _minHeight, _maxHeight);
            StopScaleTween();
            
            var currentHeight = transform.position.y;
            _pinchTweener = DOTween.To(() => currentHeight, value => currentHeight = value, height, time);
            _pinchTweener.SetEase(ease);
            _pinchTweener.OnUpdate(() =>
            {
                SetHeight(currentHeight);
                onPositionChanged?.Invoke(this);
            });
            _pinchTweener.OnComplete(() =>
            {
                SetHeight(currentHeight);
                StopScaleTween();
                onPositionChanged?.Invoke(this);
                onComplete?.Invoke();
            });
            _pinchTweener.Play();
        }

        private void StopScaleTween()
        {
            if (_pinchTweener != null)
            {
                _pinchTweener.Kill();
                _pinchTweener = null;
            }
        }
        
#if UNITY_EDITOR

        void LateUpdate()
        {
            if (Input.mouseScrollDelta.y != 0)
            {
                Scale(Input.mousePosition.x, Input.mousePosition.y, Input.mouseScrollDelta.y * 10);
                EndScale();
            }
        }

#endif
        
    }
}
